#include "mpi.h"
#include "include/rw_functions.h"
#include "include/rw_png.h"
#include <time.h>


#define MASTER_PROCESS 0


void print_matriz( int *mat, int Size, int rank );


int main( int argc, char** argv )
{
    int rank;                                    /* ID de processo */
    int numProcesos;                                    /* Total de processos */
    
    char saida[50] = "arquivo_saida.png";        /* Nome de saida padrão para a imagem a gerada */
    int *matriz = NULL;                          /* Matriz de RW */
    int Size = 1024;                             /* Tamanho do domínio, Padrão: 1024 */
    int numParticulas = 1024;                    /* Número de particulas, Padrão: 1024 */
    int *result = NULL;                          /* Matriz que receberá o resultado final */
    int opcao = 0;
    int numThreads = 2;
    
    /* Inicializando um processo MPI, Sincroniza todos os processos 
    na inicialização de uma aplicação MPI */
    MPI_Init(&argc, &argv);             
    
    
    /* Identifica qual é o processo individual que está sendo executado */
    MPI_Comm_rank( MPI_COMM_WORLD, &rank ); 
    
    
    /* Retorna o número de processos dentro de um grupo */
    MPI_Comm_size( MPI_COMM_WORLD, &numProcesos );
    
    printf("Quantidade de Argumentos passados: %d\n", argc );
    if ( argc == 1 ) {
        printf("\n[ERROR] - Entre com os argumentos necessarios.\n");
        printf("Executando Experimento Padrão\n");
        printf("Formato de execução do programa: mpiexec -n [NUMERO DE PROCESSOS] [EXECUTAVEL] [OPÇÃO] [SIZE] [PARTICULAS] [NOME_ARQUIVO] [THREADS]\n");
        printf("Onde:\n");
        printf("[OPÇÃO]         -  [0] Para Execução MPI, [1] Execução Hibrida OpenMP / MPI\n");
        printf("[SIZE]          -  Tamanho do domínio\n");
        printf("[PARTICULAS]    -  Quantidade de Particulas\n");
        printf("[NOME_ARQUIVO]  -  Nome do aquivo de saida\n");
        printf("[THREADS]       -  Quantidade de threads OpenMP por processo\n\n");
        exit(0);
    }
    
    
    if (rank == MASTER_PROCESS ) 
    {
        printf("Executando Experimento Padrão\n");
        printf("Formato de execução do programa: mpiexec -n [NUMERO DE PROCESSOS] [EXECUTAVEL] [OPÇÃO] [SIZE] [PARTICULAS] [NOME_ARQUIVO] [THREADS]\n");
        printf("Onde:\n");
        printf("[SIZE]          -  Tamanho do domínio\n");
        printf("[PARTICULAS]    -  Quantidade de Particulas\n");
        printf("[NOME_ARQUIVO]  -  Nome do aquivo de saida\n");
        printf("[THREADS]       -  Quantidade de threads OpenMP por processo\n\n");
    }
    
    
    if ( argc > 1 ){
        /* Pega a opção de execução */ 
        opcao = atoi(argv[1]);
        
        if ( argc > 2 ){
            /* Pega as dimenssões da malha */ 
            Size = atoi(argv[2]);
        
            if ( argc > 3 ){
                /* Recupera o número de particulas */
                numParticulas =  atoi(argv[3]);
                
                if ( argc > 4 ){
                    /* Recupera o nome do arquivo de saida */
                    strcpy( saida, argv[4] );
                    
                    if ( argc > 5 ){
                        /* Recupera a quantidade de threads OpenMP */
                        numThreads =  atoi(argv[5]);
                    }
                }
            }
        }
    }


    /* Inicializando a semente do gerador de numeros pseudo aleatorios */
    srand(time(NULL));
    
    /* inicializa a estrutura de números aleaorios do MT */
    initMTRefCPU("MersenneTwister.raw");
    
    unsigned int seed;

    seed = rand() + rank; 
    
    /* Execulta o RandomWalk Paralelo */
    switch(opcao)
    {
        /* Execução RW com MPI */
        case 0:
            printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
            printf("RW-MPI-Paralelo\nSize: %d x %d\nParticulas: %d\n\n", Size, Size, numParticulas/numProcesos );
            matriz = rw_serial( (numParticulas/numProcesos), Size, seed );
            printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n");
            break;
          
        /* Execução RW com Hibrido MPI/OpenMP */  
        case 1:
            printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
            printf("Processo: %d\n", rank );
            printf("RW-Hibrido-Paralelo\nSize: %d x %d\nParticulas: %d\n\n", Size, Size, numParticulas/numProcesos );
            matriz = rw_pOpenMP( numParticulas/numProcesos, 
                                 Size, 
                                 seed, 
                                 numThreads );
            printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n");
            break;
    }
    
    
    /* Apenas o processo 0 (MASTER) aloca espaço para o resultado da redução */
    if ( rank == MASTER_PROCESS )
        result = (int*) calloc ( Size * Size, sizeof(int));
    
    
    /* Realizar um Sincronisação de todos os processos! */
    MPI_Barrier(MPI_COMM_WORLD);
    
    
    /* Redução de soma para o processo de rank 0 (MASTER_PROCESS) */
    MPI_Reduce( matriz, result, Size*Size, MPI_INT, MPI_SUM, MASTER_PROCESS, MPI_COMM_WORLD );
    
    
    if ( rank == MASTER_PROCESS && matriz != NULL ) {
        printf("Total que chegaram: %d\n", result[Size * Size - 1] );
        printf("Probabilidade: %f\n", (result[Size*Size-1] / (double)numParticulas) );
        writeImage( saida, matriz, "This is a Random Walk result's image", Size);
    }
   

    /* 
     * Chamda de encerramento do MPI 
     * Usada para liberar memória 
     */
    MPI_Finalize();
    
    return EXIT_SUCCESS;
}


void print_matriz( int *mat, int Size, int rank )
{
    int i, j;
    
    printf("Matriz gerada pelo processo: %d\n", rank );
    
    for ( i = 0; i < Size; i++ ){
        for ( j = 0; j < Size; j++ )
            printf("%5d", mat[i*Size+j] );
        printf("\n");
    }
    printf("\n");
}
